/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* $Id: unix_socket.c 32 2007-07-13 03:08:09Z kf701.ye $ */

#include <sys/socket.h>
#include <sys/un.h>

#include "netutils.h"
#include "debug.h"

/**
 * @brief open a new UNIX SOCKET fd
 *
 * @return socket fd or -1 on error
 */
int unix_socket_new(void)
{	
	int sockfd = socket (AF_LOCAL, SOCK_DGRAM, 0);
	struct timeval tv;
	tv.tv_sec = 2;
	tv.tv_usec = 0;	
	if ( sockfd < 0 )
		return -1;
	if (setsockopt(sockfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) < 0)
	{
		close(sockfd);
		return -1 ;
	}		
	return sockfd;
}

/**
 * @brief free a UNIX SOCKET fd and related path
 */
void unix_socket_free(int sockfd)
{
	if ( sockfd < 0 )
		return ;
	
	struct sockaddr_un unaddr;		
	socklen_t len = sizeof(unaddr);
	memset(&unaddr, 0, len);
	int ret = getsockname ( sockfd , (struct sockaddr*)&unaddr , &len );
	close(sockfd);
	if ( ret == 0 && strlen(unaddr.sun_path) > 0 )
		unlink(unaddr.sun_path);
	return ;
}

/**
 * @brief open a new UNIX SOCKET fd and bind on UNIX_PATH
 *
 * @return socket fd or -1 on error
 */
int unix_socket_new_listen(const char *unix_path) 
{
	if( NULL == unix_path )
		return -1;

	int listenfd;
	struct sockaddr_un servaddr;	
	
	unlink( unix_path ) ;
	if ( 0 == access(unix_path, F_OK) )
		return -1;
	listenfd = socket (AF_LOCAL, SOCK_DGRAM, 0);	
	if ( listenfd < 0 )
		return -1;
	
	bzero (&servaddr, sizeof (servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy (servaddr.sun_path,unix_path);
		
	if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof (servaddr)) < 0) 
	{
		unlink(unix_path);
		close(listenfd);
		return -1;
	} 
	struct timeval tv;
	tv.tv_sec = 2;
	tv.tv_usec = 0;	
	if (setsockopt(listenfd,SOL_SOCKET,SO_RCVTIMEO,&tv,sizeof(tv)) < 0)
	{
		unlink(unix_path);
		close(listenfd);
		return -1 ;
	}		
	return listenfd;
}

/**
 * @brief send data to unix socket through to_path
 *
 * @param sockfd unix socket fd
 * @param to_path send target path
 * @param data send data
 * @param size of data
 *
 * @return 
 * On success, these calls return the number of characters sent. 
 * On error, -1 is returned, and errno is set appropriately.
 */
int32_t unix_socket_send(int sockfd , const char *to_path, const char *data, uint32_t size)
{
	struct sockaddr_un to_addr;	
	memset(&to_addr, 0, sizeof (to_addr));
	to_addr.sun_family = AF_LOCAL;
	strcpy (to_addr.sun_path , to_path); 
	int32_t ret = sendto(sockfd , data , size ,0, (struct sockaddr*)&to_addr,sizeof(to_addr));
	return ret;
}

/** 
 * @brief UNIX SOCKET server use select
 *
 * Open a listen socket and read data,
 * then deleve data to FUNC, the user defined 
 * FUNC deal with data.
 * 
 * Note: dup sun_path and buf before create thread in FUNC
 *
 * @param unix_path listen path
 * @param psize the UDP server MAX size protocol data
 * @param func user defined FUNC for deal with data
 */
void unix_socket_server( const char *unix_path, uint32_t psize, un_data_func func)
{
	if( psize <= 0 || func == NULL )
	{
		sys_message("%s,%d: argu err\n", __FILE__, __LINE__);
		return ;
	}
	
	int sockfd = unix_socket_new_listen( unix_path );
	if( -1 == sockfd )
	{
		sys_message("%s,%d: open sock err,%m\n", __FILE__, __LINE__);
		return ;
	}
	struct sockaddr_un  addr;
	socklen_t len = sizeof( struct sockaddr_un );
	
	fd_set readset;
	FD_ZERO(&readset);
	FD_SET( sockfd , &readset );
	
	int max_fd = sockfd , select_ret, nread;
	uint8_t buf[ psize ];
	
	while( 1 )
	{
		select_ret = select(max_fd+1 , &readset , NULL , NULL , NULL);
		if ( select_ret < 0 )
		{
			sys_message("%s,%d: select err,%m\n", __FILE__, __LINE__);
			continue;
		}	
		if (  FD_ISSET ( sockfd , &readset)  )
		{
			nread = recvfrom(sockfd, (void*)buf, sizeof(buf) ,0, (struct sockaddr*)&addr, &len);
			if( nread <= 0 )
			{
				sys_message("%s: recvfrom err\n", __func__);
				continue;
			}
			sys_log("%s,%d: read from = %s\n", __FILE__, __LINE__, addr.sun_path);
			sys_log("%s,%d: read data len = %d\n", __FILE__, __LINE__, nread);
			/* Note: dup sun_path and buf before create thread in func */
			func( addr.sun_path, buf, nread );
		}
	}
}
